/***************************************************************************\
 * The Mattise Kernel														*
 * Copyright 2007 Matthew Iselin											*
 * Licensed under the GPL													*
 *																			*
 * kernel.cc																*
 *																			*
 * The entry point to the C++ part of the kernel, handles a lot of runtime	*
 * stuff and gets the kernel ready to start interacting with the user.		*
 *																			*
\***************************************************************************/

// includes
#include <system.h>
#include <iostream>
#include <console.h>
#include <memory.h>
#include <task.h>
#include <disk.h>
#include <gui.h>
#include <keyboard.h>
#include <filesys.h>
#include <multiboot.h>
#include <stdio.h>
#include <paging.h>
#include <stddef.h>
#include <io.h>
#include <cpu.h>
#include <string.h>
#include <reloc_elf.h>
#include <pm.h>	// -- kmcguire 10/6/2007
#include <filesystem/fat32.h>
#include <syscall.h>

/// The global console
Console cons;

/// The DSO handle - only the address of this symbol is taken by GCC
void *__dso_handle;

/// A global object - we support a maximum of 32
struct object
{
	void (*f)(void*);
	void *p;
	void *d;
} object[32] = {0};

/// Number of global objects
unsigned int iObject = 0;

/// Sets up the destructors for all global objects
extern "C" int __cxa_atexit(void (*f)(void *), void *p, void *d)
{
	if (iObject >= 32) return -1;
	object[iObject].f = f;
	object[iObject].p = p;
	object[iObject].d = d;
	++iObject;
	return 0;
}

/// This destroys all objects upon exiting main().
/// This function will never get called as we never
/// return from main().
extern "C" void __cxa_finalize(void *d)
{
	unsigned int i = iObject;
	for (; i > 0; --i)
	{
		--iObject;
		object[iObject].f(object[iObject].p);
	}
}

/// The ABI requires a 64-bit type
__extension__ typedef int __guard __attribute__((mode (__DI__)));

/// Acquires a guard for a static variable
extern "C" int __cxa_guard_acquire( __guard* g )
{
	return !*(char*)(g);
}

/// Releses a guard for a static variable
extern "C" void __cxa_guard_release( __guard* g )
{
	*(char*)g = 1;
}

/// Aborts a guard for a static variable
extern "C" void __cxa_guard_abort( __guard* g )
{
}

/// A pure virtual function failed, so this function was called.
extern "C" void __cxa_pure_virtual()
{
	kprintf( "Fatal Error: Pure virtual function call (=0) caught!\n" );
	while( 1 );
}

/// GCC's stack check failed!
extern "C" void __stack_chk_fail()
{
	kprintf( "Fatal Error: Stack is corrupt!\n" );
	while( 1 );
}

/// Overloads the operator "new"
void* operator new (size_t size)
{
    return kmalloc(size);
}

/// Overloads the operator "new[]"
void* operator new[] (size_t size)
{
    return kmalloc(size);
}

/// Overloads the operator "delete"
void operator delete (void * p)
{
    kfree( p );
}

/// Overloads the operator "delete[]"
void operator delete[] (void * p)
{
    kfree( p );
}

/// STDOUT character output. Prints a character to the screen
int stdout_putc( int c )
{
	// print it (this hack is to get it to print safely)
	char tmp[2] = { c, 0 };
	cons.Output( tmp );
}

/// STDIN character input. Reads a character from the user
int stdin_getc()
{
	// get from the keyboard buffer
	
	// get the character
	char c = getch();
	
	// return it
	return c;
}

/// STDOUT 'write' function. Writes data to the console
int stdout_write( int fd, void* buff, uint_t count )
{
	// create a temporary char* pointer to the void* buffer
	char* tmp = (char*) buff;
	
	// print it all
	tmp[count] = 0;
	cons.Output( tmp );
	
	// return the count
	return count;
}

/// STDIN 'read' function. Reads data from the user into a buffer
int stdin_read( int fd, void* buff, uint_t count )
{
	// create a temporary char* pointer to the void* buffer
	char* tmp = (char*) buff;
	
	// we need interrupts for the keyboard IRQ to fire
	asm volatile( "sti" );
	
	// get the data
	int i;
	for( i = 0; i < count; i++ )
	{
		char c = getch();
		kprintf( "%c", c );
		if( c == '\n' )
			break;
		tmp[i] = c;
	}
	tmp[i] = 0;
	
	// no more interrupts
	asm volatile( "cli" );
	
	// return the count
	return i;
}

/// Global multiboot info pointer
multiboot_info_t* MultiBootHeader;

/// Program execution request type
struct ProgramExecution {
	int msgcode; // backup message code
	char path[255]; // path to the executable
	int pathlen;
	char argv[2048]; // arguments - parsable by the kernel
	int argvlen;
	char env[1024]; // environment - parsable by the kernel
	int envlen;
};

/// Userland heap
char* uspace_heap;

extern struct tagStruct ptable[];

// a couple of threads
void thread1()
{
	kprintf("thread1\n");

	kprintf( "stack dumps of pid=0 and pid=1\n" );
	dump_stack( (uint_t*) ptable[0].esp );
	dump_stack( (uint_t*) ptable[1].esp );

	//resched();

	while( 1 );
}
void thread2()
{
	kprintf("thread2\n");

	resched();

	while( 1 );
}
void phpscript()
{
	kprintf("vfs.php is really complex\n");

	resched();

	while( 1 );
}

/// Idle procedure. Loads all modules and handles any special requests
void nullproc()
{	
	// get the module list
	module_t* mods = (module_t*) MultiBootHeader->mods_addr;
	
	// temporary ELF relocation structure
	ELFRelocation tmpreloc;

	// make some threads
	CreateThread((unsigned int)thread1,1,"thread1");
	//CreateThread((unsigned int)thread2,1,"thread2");
	//CreateThread((unsigned int)phpscript,1,"notes about php" );

	// enable interrupts now that we're setup
//	asm( "sti" );

	resched();

	kprintf( "back to task 1\n" );

	while( 1 );

	// allocate the userspace heap
	uspace_heap = (char*) pm_alloc( (1024*1024*32) >> 12 );

	// to facilitate testing
	kprintf( "\n" );

	// map it in
	// map in the userspace heap
	for( int i = 0; i < ((1024*1024*32) >> 12); i++ )
		MapUserPhysicalAddress( 0xE0000000 + (i*0x1000), (uint_t) (uspace_heap + (i*0x1000)), PF_PRESENT | PF_READWRITE | PF_USER );

	// disable interrupts so that states don't get corrupted
	asm volatile( "cli" );

	// loop through and load each module
	for( int i = 0; i < MultiBootHeader->mods_count; i++ )
	{
		// load it, and execute it
		tmpreloc.LoadFile( mods[i].mod_start );
		tmpreloc.RelocateExecutable();
	}

	// enable interrupts again
	asm volatile( "sti" );

	// enter our own loop
	while( true )
	{
		// check if we have any messages
		if( HasMessage( currpid() ) )
		{
			// tell me
			kprintf( "i have a message!\n" );

			// get it
			struct tagMsg msg;
			GetMessage( currpid(), &msg );
			kprintf( "code is %d\n", msg.msgCode );
			kprintf( "data is %s [0x%x]\n", msg.destAddr, msg.destAddr );
			ufree( (void*) msg.destAddr );
		}
			/*
			// get the message
			struct regs r;
			r.eax = 28;
			SysCall( &r );
			
			// get the message
			struct ProgramExecution* pexec = (struct ProgramExecution*) r.esi;
			// read in all the data
	pexec->msgcode = 0x64;
	pexec->pathlen = sprintf( pexec->path, "%s", name );
	pexec->argvlen = sprintf( pexec->argv, "%s", argv );
	pexec->envlen = sprintf( pexec->env, "%s", env );
		}*/
		
		// halt
		asm( "hlt" );
	}
	
	// currently Mattise is running several modules and this process
	// which runs when nothing else can
}

/// Start of kernel image
extern unsigned char __end;

/// End of kernel image
extern unsigned char __start;

/// Total available RAM to work in
unsigned long __mb_totmem;

/// Entry point to the kernel. This sets up all kernel-based stuff and then begins the kernel process.
extern "C" int main( multiboot_info_t* mb, unsigned int magic_number )
{
	// kill interrupts
	__asm__ __volatile__ ( "cli" );
	
	// set the global multiboot header
	MultiBootHeader = mb;

	// the module list
	module_t* mods = (module_t*) MultiBootHeader->mods_addr;
	
	// total available memory
	__mb_totmem = ( mb->mem_lower + mb->mem_upper ) * 1024;
	
	// turn on debugging
	cons.ToggleDebugMode();
	
	// clear the screen
	cons.ClearScreen();

	// setup paging
	cons.Output( "[MEM ] Setting up paging... ", true );
	InitPaging();
	cons.Output( "Done!\n", true );
	
	// install the page allocator
	cons.Output( "[PM  ] Installing page manager... ", true );

	// initialize the memory manager
	pm_init();
	
	// finally, fill from the memory map
	pm_fill_from_mmap( mb->mmap_addr, mb->mmap_length );
	
	// setup regions of memory where memory must not be allocated in (from the start of the kernel, 4 MB)
	// an extra 1 is added to the kernel one in case it isn't page-aligned
	pm_set( ( (uint_t) &__start ), ( ( ( (uint_t) &__end - (uint_t) &__start ) / 4096 ) + 1 ) + 1 );
	pm_set( 0xB8000, (((80*25)*2) / 4096) + 1 );
	
	// keep the first MB unavailable
	pm_set( 0x0, 0x100 );
	
	// modules are set as used too
	for( int i = 0; i < MultiBootHeader->mods_count; i++ )
	{
		// don't let module space be allocated
		pm_set( mods[i].mod_start, ((mods[i].mod_end - mods[i].mod_start)/4096) + 1 );

		// map the space so that we don't page fault when accessing it
		for( int p = mods[i].mod_start; p < mods[i].mod_end; p += 0x1000 )
			MapPhysicalAddress( read_cr3(), p, p, PF_SUPERVISOR | PF_PRESENT | PF_READWRITE );
	}

	// map the memory bitmap
	pm_map();

	// completed installing the page manager
	cons.Output( "Done!\n", true );
	
	// install the GDT
	cons.Output( "[GDT ] Installing GDT... ", true );
	gdt_install();
	cons.Output( "Done!\n", true );
	
	// install the IDT
	cons.Output( "[IDT ] Installing IDT... ", true );
	idt_install();
	cons.Output( "Done!\n", true );
	
	// install ISRs
	cons.Output( "[INT ] Installing interrupts (ISR/IRQ)... ", true );
	isrs_install();
	
	// set up IRQ mapping
	irq_install();
	cons.Output( "Done!\n", true );
	
	// initialize the memory manager
	cons.Output( "[MEM ] Installing memory manager... ", true );
	kmalloc( 1 );
	cons.Output( "Done!\n", true );
	
	// do CPUID
	cons.Output( "[CPU0] Executing CPUID... ", true );
	CPUIdent();
	cons.Output( "Done!\n", true );

	// install the timer
	cons.Output( "[TMR ] Installing timer... ", true );
	install_timer();
	cons.Output( "Done!\n", true );
	
	// install the keyboard
	cons.Output( "[KBD ] Installing keyboard... ", true );
	keyboard_install();
	cons.Output( "Done!\n", true );

	// setup the multitasker
	cons.Output( "[MTSK] Initializing multitasking... ", true );
	initmt();
	cons.Output( "Done!\n", true );
	
	// stdout and stdin device descriptors
	int consdev;
	
	// install stdout and stdin (both refer to the same device)
	consdev = InstallDevice( "console", 0, 0 );
	
	// install the functions (both stdin and stdout are the same)
	INSTFUNCDATA consoleFunctions = { (IOCtlIntFunc) NULL, (IOCtlPtrFunc) NULL, stdin_read, stdout_write, (LSeekFunc) NULL, stdin_getc, stdout_putc };
	InstallFunctions( consdev, consoleFunctions );
	
	// setup stdout, stdin and stderr (0,1,2)
	int stdin = open( "console", READONLY );
	int stdout = open( "console", WRITEONLY );
	int stderr = open( "console", READWRITE );
	
	// create system task
	cons.Output( "[MTSK] Creating idle task and enabling tasking... ", true );
	CreateThread( (unsigned int) nullproc, 5, "System Idle Process", 1 );
	cons.Output( "Done!\n", true );

	// enable multitasking
	mtenable();

	// reschedule (just basically switches the stacks over)
	resched();

	// loop forever
	while( true );
}
